package online.flytool.base.refect;

import online.flytool.base.check.Assert;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * 避免和高性能cglib的命名冲突,所以加上fly前缀
 * @author ：lingchong
 * @date ：Created in 2022/7/16 11:36
 * @description：实体复制机
 * @modified By：
 * @version: $
 */
public class FlyBeanCopier {

    public void copyProp(Object source, Object target, String... ignoreProperties)  {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");
        Class<?> actualEditable = target.getClass();

        //  获取内省属性
        Map<String, PropertyDescriptor> targetPdMap = getPropertyDescriptors(actualEditable);
        Map<String, PropertyDescriptor> sourcePdMap = getPropertyDescriptors(actualEditable);
        List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;

        for (PropertyDescriptor targetPd : targetPdMap.values()) {
            Method writeMethod = targetPd.getWriteMethod();
            //  是否可以写入
            if(writeMethod==null  ){
                continue;
            }
            //  是否是属于忽略中断
            if(ignoreList!=null && ignoreList.contains(targetPd.getName()) ){
                continue;
            }
            PropertyDescriptor sourcePd = sourcePdMap.get(targetPd.getName());
            if (sourcePd == null) {
                continue;
            }
            //  属性是否可读
            Method readMethod = sourcePd.getReadMethod();
            if(readMethod==null){
                continue;
            }
            //  是否类型可以对等分配
            if(!ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())){
                continue;
            }
            try {
                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                    readMethod.setAccessible(true);
                }
                Object value = readMethod.invoke(source);
                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                    writeMethod.setAccessible(true);
                }
                writeMethod.invoke(target, value);
            }
            catch (Throwable ex) {
                throw new RuntimeException(
                        "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
            }
        }

    }


    public Map<String,PropertyDescriptor> getPropertyDescriptors(Class<?> clazz)  {
        //  TODO 后期加上缓存
        Map<String,PropertyDescriptor> propDescMap=new LinkedHashMap<>();
        BeanInfo beanInfo=getBeanInfo(clazz);
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor item : propertyDescriptors) {
            propDescMap.put(item.getName(),item);
        }
        return propDescMap;
    }

    /**
     * Java内省的结果会缓存在ThreadGroupContext中，并且通过synchonrized关键字对缓存加锁(下图中的红框部分)，导致同一个线程组中的线程无法并行内省。
     * 后期加上一个自己的缓存
     * @param clazz
     * @return
     */
    private BeanInfo getBeanInfo(Class<?> clazz)  {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
            return beanInfo;
        } catch (IntrospectionException e) {
            throw new RuntimeException("内省bean时出错",e);
        }
    }

}